package ch.fhnw.apsi.filter;

import java.io.IOException;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Pattern;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import ch.fhnw.apsi.Page;

/**
 * Filter for "hardening" the Servlet. Any suspicious input is neglected.
 */
@WebFilter(filterName = "filter-security", description = "Check the user input.", urlPatterns = { "/*" })
public final class FilterSecurity implements Filter {

  public FilterSecurity() {
  }

  private static final Pattern PARAMNAME = Pattern.compile("[a-z0-9]{1,20}", Pattern.CASE_INSENSITIVE);
  private static final Pattern URLENCODING = Pattern.compile(".*(%[0-9a-fA-F]{2}|&.*?;).*");
  private static final Pattern SCRIPT = Pattern.compile(".*<SCRIPT.*", Pattern.CASE_INSENSITIVE);

  @Override
  public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain)
      throws IOException, ServletException {
	
	// ensure the request is using https
	if (!request.isSecure()) {
	  if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) {
			HttpServletRequest httpReq = (HttpServletRequest) request;
			HttpServletResponse httpResp = (HttpServletResponse) response;
			String url = httpReq.getRequestURL().toString();
			url = url.replaceFirst("http", "https");
			url = url.replaceFirst(":8080", ":8443");
			httpResp.sendRedirect(url);
			// nothing to further filter/process in this request
			return;
	  }
	}
	
    try {
      final Map<String, String[]> parameterMap = request.getParameterMap();
      for (final Entry<String, String[]> entry : parameterMap.entrySet()) {
        final String key = entry.getKey();
        // Check the names of the parameters:
        if (key.isEmpty() || !PARAMNAME.matcher(key).matches())
          throw new SecurityException();
        // Check the values:
        final String[] values = entry.getValue();
        for (final String value : values) {
          // We do not allow any %XX or &XX; in the values!
          if (URLENCODING.matcher(value).matches())
            throw new SecurityException();
          // Injection of JavaScript:
          if (value.contains("<"))
            if (SCRIPT.matcher(value).matches())
              throw new SecurityException();
          // Possible injection of SQL:
          if (containsAny(value, "--", "#", "\"", "'"))
            if (containsAny(value.toLowerCase(), "drop", "where", "select", "=", "or", "exec"))
              throw new SecurityException();
        }
      }
    } catch (final SecurityException e) {
      // This will cause the system to load an error site.
      request.setAttribute("page", Page.ERROR);
      request.setAttribute("status", 403);
      request.setAttribute("error", "Security Alert! Authorities have been informed.");
    }

    // Some more Security by HTTP-headers:
    try {
      final HttpServletResponse resp = (HttpServletResponse) response;
      resp.setHeader("X-Frame-Options", "Deny");
      resp.setHeader("X-Content-Type-Options", "nosniff");
      resp.setHeader("X-XSS-Protection", "1; mode=block");
      // http://www.w3.org/TR/CSP/#default-src
      resp.setHeader("Content-Security-Policy", "default-src 'self'");
      resp.setHeader("X-Content-Security-Policy", "default-src 'self'");
      resp.setHeader("X-WebKit-CSP", "default-src 'self'");
      // http://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security
    	resp.setHeader("Strict-Transport-Security", "max-age=31536000");
    } catch (final Exception e) {
      // Well, maybe this servlet doesn't allow headers or something...
    }

    // pass the request along the filter chain
    chain.doFilter(request, response);
  }

  private static boolean containsAny(final String heystack, final String... needle) {
    for (final String string : needle)
      if (heystack.contains(string))
        return true;
    return false;
  }

  @Override
  public void destroy() {
    // ...
  }

  @Override
  public void init(final FilterConfig arg0) throws ServletException {
    // ...
  }
}
